/**
 *Copyright 2013 by dragon.
 *
 *File name: Downloader.java
 *Author:      dragon
 *Email:       fufulove2012@gmail.com
 *Blog:        http://blog.csdn.net/xidomlove
 *Version:     1.0.0
 *Date:        2013-10-4 下午2:51:34
 *Description: FTP,HTTP 共同操作,记录下载信息,写入文件等
 */
package com.dragon.jaxel.xtp;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.RandomAccessFile;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;

import com.dragon.jaxel.Downloader;
import com.dragon.log.Logger;

/**
 * @author dragon8
 * 
 */
/**
 * @author dragon8
 * 
 */
public abstract class XtpDownloader implements Downloader {

	/**
	 * 总连接数
	 */
	private int connectionCount;

	/**
	 * 文件大小
	 */
	private long size = -1;

	/**
	 * 下载线程的数组
	 */
	private DownloadConnection[] downloadConnections;

	/**
	 * 保存的文件名
	 */
	protected String savePathString;

	/**
	 * @return the savePathString
	 */
	@Override
	public String getSavePathString() {
		return savePathString;
	}

	/**
	 * 当前是否正在下载
	 */
	private boolean bRunning = false;

	private int aliveConnectionCount;

	/**
	 * 可随机写文件
	 */
	private RandomAccessFile randomAccessFile;

	/**
	 * 
	 */
	private FileChannel fileChannel;

	/**
	 * 获取活跃的连接
	 * 
	 * @return the aliveConnectionCount
	 */
	public int getAliveConnectionCount() {
		return aliveConnectionCount;
	}

	/**
	 * 所有下载线程是否已结束，有可能被中断，不一定已下载完成
	 * 
	 * @return
	 */
	@Override
	public boolean isDownLoading() {
		return aliveConnectionCount > 0;
	}

	/**
	 * 是否文件已经下载完成
	 * 
	 * @return
	 */
	@Override
	public boolean isCompleted() {
		return getProgress() == getSize();
	}

	/**
	 * 获取文件长度
	 * 
	 * @return the size
	 */
	@Override
	public long getSize() {
		if (size == -1) {
			size = getFileLength();
			if (size == -1) {
				Logger.getDefaultLogger().error("Get file size failed");
			}
		}
		return size;
	}

	/**
	 * 获取总进度
	 * 
	 * @return
	 */
	@Override
	public long getProgress() {
		long progress = 0;
		for (int i = 0; i < connectionCount; i++) {
			progress += downloadConnections[i].bytesRead;
		}
		return progress;
	}

	/**
	 * 
	 */
	protected XtpDownloader() {
		// TODO Auto-generated constructor stub
	}

	/**
	 * 删除状态文件
	 */
	@Override
	public void deleteSateFile() {
		File file = new File(savePathString + ".jaxel");
		if (file.exists()) {
			file.delete();
		}
	}

	/**
	 * 保存下载信息到文件， 文件结构：<br/>
	 * 1.string:class name. <br/>
	 * 2.int: connection count.<br/>
	 * 3.connetcions[];connections state.<br/>
	 * 4.savePath <br/>
	 * 5.subclass state.
	 * 
	 * @throws IOException
	 */
	@Override
	public void saveState() throws IOException {
		ObjectOutputStream outputStream = new ObjectOutputStream(
				new FileOutputStream(savePathString + ".jaxel"));
		outputStream.writeUTF(this.getClass().getName());
		outputStream.writeInt(connectionCount);
		for (int i = 0; i < connectionCount; i++) {
			outputStream.writeLong(downloadConnections[i].bytesBegin);
			outputStream.writeLong(downloadConnections[i].bytesEnd);
			outputStream.writeLong(downloadConnections[i].bytesRead);
		}
		outputStream.writeUTF(savePathString);
		saveDowanloadState(outputStream);
		outputStream.close();
	}

	/**
	 * 读取上次未完成下载
	 * 
	 * @param inputStream
	 * @return
	 * @throws FileNotFoundException
	 * @throws IOException
	 * @throws ClassNotFoundException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 * @throws SecurityException
	 */
	@Override
	public void loadState(ObjectInputStream inputStream)
			throws FileNotFoundException, IOException, ClassNotFoundException,
			InstantiationException, IllegalAccessException,
			IllegalArgumentException, InvocationTargetException,
			NoSuchMethodException, SecurityException {
		connectionCount = inputStream.readInt();
		downloadConnections = new DownloadConnection[connectionCount];
		for (int i = 0; i < connectionCount; i++) {
			downloadConnections[i] = new DownloadConnection();
			downloadConnections[i].bytesBegin = inputStream.readLong();
			downloadConnections[i].bytesEnd = inputStream.readLong();
			downloadConnections[i].bytesRead = inputStream.readLong();
		}
		savePathString = inputStream.readUTF();
		loadDowanloadState(inputStream);
		inputStream.close();
	}

	/**
	 * 子类保存需要保存到未完成下载的状态
	 * 
	 * @param outputStream
	 * @throws IOException
	 */
	protected abstract void saveDowanloadState(ObjectOutputStream outputStream)
			throws IOException;

	/**
	 * 读取上次未完成下载的状态，子类实现
	 * 
	 * @param inputStream
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	protected abstract void loadDowanloadState(ObjectInputStream inputStream)
			throws IOException, ClassNotFoundException;

	/**
	 * 开始下载
	 * 
	 * @return 如果已经在下载或者上一次结束未完成（线程没有退出），则返回false
	 * @throws IOException
	 */
	@Override
	public boolean start() throws IOException {
		long len = getSize();
		int count;
		if (len < 5 * 1024 * 1024) {
			count = 2;
		} else if (len < 100 * 1024 * 1024) {
			count = 4;
		} else {
			count = 8;
		}
		return start(count);
	}

	/**
	 * 停止下载
	 * 
	 */
	@Override
	public void stop() {
		for (int i = 0; i < connectionCount; i++) {
			if (downloadConnections[i].inputStream != null) {
				synchronized (downloadConnections[i].inputStream) {
					if (downloadConnections[i].inputStream != null) {
						try {
							downloadConnections[i].inputStream.close();
						} catch (Exception e) {
							// TODO Auto-generated catch block
							Logger.getDefaultLogger().debug(e.getMessage());
						}
					}
				}
			}
		}
	}

	/**
	 * 开始下载任务
	 * 
	 * @param connectionCount
	 * @return 如果已经在下载或者上一次结束未完成（线程没有退出）,或者获取文件长度失败，则返回false
	 * @throws IOException
	 */
	@Override
	public boolean start(int connectionCount) throws IOException {
		this.connectionCount = connectionCount;
		// 初始化连接
		if (getSize() == -1 || connectionCount <= 0) {
			return false;
		}
		long bytesPerConnetion = getSize() / connectionCount;
		downloadConnections = new DownloadConnection[connectionCount];
		long offset = 0;
		int i = 0;
		for (; i < (connectionCount - 1); i++) {
			downloadConnections[i] = new DownloadConnection(offset, offset
					+ bytesPerConnetion);
			offset += bytesPerConnetion;
			downloadConnections[i].connectionName = "" + i;
		}

		downloadConnections[i] = new DownloadConnection(offset, getSize());
		downloadConnections[i].connectionName = "" + i;

		// 调用resume 开始启动线程
		return resume();
	}

	/**
	 * 继续下载
	 * 
	 * @return 如果已经在下载或者上一次结束未完成（线程没有退出），则返回false
	 * @throws IOException
	 */
	@Override
	public boolean resume() throws IOException {
		if (getSize() == -1 || connectionCount <= 0) {
			return false;
		}
		if (isDownLoading()) {
			// 等待100毫秒
			for (int i = 0; i < 10; i++) {
				try {
					Thread.sleep(10);
					if (!isDownLoading()) {
						break;
					}
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					Logger.getDefaultLogger().debug(e.getMessage());
				}
			}
		}
		if (isDownLoading()) {
			return false;
		}
		bRunning = true;
		aliveConnectionCount = connectionCount;
		File file = new File(savePathString);
		randomAccessFile = new RandomAccessFile(file, "rw");
		Logger.getDefaultLogger().info(
				String.format("File open: %s", file.getAbsolutePath()));
		fileChannel = randomAccessFile.getChannel();
		fileChannel.truncate(getSize());
		for (int i = 0; i < connectionCount; i++) {
			downloadConnections[i].start();
		}
		return true;
	}

	/**
	 * 暂停下载
	 * 
	 */
	@Override
	public void pause() {
		for (int i = 0; i < connectionCount; i++) {
			if (downloadConnections[i].inputStream != null) {
				synchronized (downloadConnections[i].inputStream) {
					if (downloadConnections[i].inputStream != null) {
						try {
							downloadConnections[i].inputStream.close();
						} catch (Exception e) {
							// TODO Auto-generated catch block
							Logger.getDefaultLogger().debug(e.getMessage());
						}
					}
				}
			}
		}
	}

	/**
	 * 获取文件长度，需要子类实现
	 * 
	 * @return
	 */
	abstract long getFileLength();

	/**
	 * 获取下载协议的字符串表示
	 * 
	 * @return
	 */
	public abstract String getProtocol();

	/**
	 * 打开一个连接，需要子类实现
	 * 
	 * @param context
	 *            供保存连接上下文信息
	 * @param bytesBegin
	 *            该连接起始下载偏移
	 * @param bytesEnd
	 *            下载结束位置
	 * @return 输入流，用于读取并写入文件
	 * @throws IOException
	 */
	abstract InputStream openConnection(ConnectionContext context,
			final long bytesBegin, final long bytesEnd) throws IOException;

	protected void closeConnection(ConnectionContext context)
			throws IOException {
	}

	/**
	 * 一个连接即将开始，可根据需要重写方法
	 * 
	 * @param context
	 */
	protected void onBegin(ConnectionContext context) {
	}

	/**
	 * 强制释放内存映射
	 * 
	 * @param mappedByteBuffer
	 */
	static void unmapFileChannel(final MappedByteBuffer mappedByteBuffer) {
		try {
			if (mappedByteBuffer == null) {
				return;
			}
			mappedByteBuffer.force();
			AccessController.doPrivileged(new PrivilegedAction<Object>() {
				@Override
				public Object run() {
					try {
						Method getCleanerMethod = mappedByteBuffer.getClass()
								.getMethod("cleaner", new Class[0]);
						getCleanerMethod.setAccessible(true);
						sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod
								.invoke(mappedByteBuffer, new Object[0]);
						cleaner.clean();
					} catch (Exception e) {
						e.printStackTrace();
					}
					return null;
				}
			});

		} catch (Exception e) {
			Logger.getDefaultLogger().debug(
					String.format("Unmap file failed!%s", e.getMessage()));
		}
	}

	/**
	 * 连接类，表示一个下载连接
	 * 
	 * @author dragon8
	 * 
	 */
	class DownloadConnection implements Runnable {

		private ConnectionContext context = new ConnectionContext();

		/**
		 * 下载开始偏移
		 */
		long bytesBegin;

		/**
		 * 下载结束的位置
		 */
		long bytesEnd;

		/**
		 * 总共已完成字节数
		 */
		long bytesRead;

		final static int BUFFER_SIZE = 64 * 1024;

		/**
		 * 最大内存文件缓冲区10MB
		 */
		final static int MAX_MEMORY_BUFFER_SIZE = 10 * 1024 * 1024;

		/**
		 * 网络连接的流
		 */
		InputStream inputStream = null;

		/**
		 * 连接名字，方便日志查看
		 */
		String connectionName = "unknown";

		/**
		 * 线程是否正在运行
		 */
		boolean bIsRunning;

		public DownloadConnection() {
			super();
			// TODO Auto-generated constructor stub
		}

		public DownloadConnection(long bytesBegin, long bytesEnd) {
			super();
			this.bytesBegin = bytesBegin;
			this.bytesEnd = bytesEnd;
			bytesRead = 0;
		}

		public void start() {
			bIsRunning = true;
			new Thread(this).start();
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Thread#run()
		 */
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Runnable#run()
		 */
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Runnable#run()
		 */
		@Override
		public void run() {
			// TODO Auto-generated method stub
			try {

				// 该文件块已经下载完成
				if ((bytesRead + bytesBegin) >= bytesEnd) {
					throw new IOException("Connection has finished");
				}

				// 即将开始下载
				onBegin(context);

				// 打开连接
				inputStream = new BufferedInputStream(openConnection(context,
						bytesBegin + bytesRead, bytesEnd));

				// 总共需要读取的数据大小
				long bytesToRead = bytesEnd - bytesBegin;

				// 本阶段需要读取的数据大小
				long stageToRead = bytesToRead - bytesRead;
				if (stageToRead > MAX_MEMORY_BUFFER_SIZE) {
					stageToRead = MAX_MEMORY_BUFFER_SIZE;
				}
				// 本阶段已完成下载
				long stageRead = bytesRead;
				int len;
				byte[] buffer = new byte[BUFFER_SIZE];

				Logger.getDefaultLogger()
						.info(String
								.format("Connection:%s Start,download offset:%d, end:%d",
										connectionName, bytesBegin, bytesEnd));

				// 映射内存以快速写文件
				MappedByteBuffer mappedByteBuffer = fileChannel.map(
						FileChannel.MapMode.READ_WRITE, bytesBegin + bytesRead,
						stageToRead);

				Logger.getDefaultLogger().debug(
						String.format(
								"Connection:%s Map memory, offset:%d, size:%d",
								connectionName, bytesBegin + bytesRead,
								stageToRead));
				try {
					while (bRunning) {
						// 网络读取
						len = inputStream.read(buffer);
						if (len == -1) {
							Logger.getDefaultLogger()
									.info(String
											.format("Connection:%s Network disconnected,bytes has read:%d",
													connectionName, bytesRead));
							break;
						}
						if ((bytesRead + len) >= bytesToRead) {
							// 本文件块已全部完成
							len = (int) (bytesToRead - bytesRead);
							mappedByteBuffer.put(buffer, 0, len);
							bytesRead += len;
							Logger.getDefaultLogger().info(
									String.format(
											"Connection:%s Download finished",
											connectionName));
							break;
						} else if ((bytesRead + len - stageRead) >= stageToRead) {
							// 本阶段内存缓冲区写满，移动到下一区域块
							int tmpLen = (int) (stageToRead - bytesRead + stageRead);
							mappedByteBuffer.put(buffer, 0, tmpLen);
							bytesRead += tmpLen;
							stageRead = bytesRead;
							unmapFileChannel(mappedByteBuffer);
							stageToRead = bytesToRead - bytesRead;
							if (stageToRead > MAX_MEMORY_BUFFER_SIZE) {
								stageToRead = MAX_MEMORY_BUFFER_SIZE;
							}
							mappedByteBuffer = fileChannel.map(
									FileChannel.MapMode.READ_WRITE, bytesBegin
											+ bytesRead, stageToRead);
							// 将剩下内容写入下一文件块
							mappedByteBuffer.put(buffer, tmpLen, len - tmpLen);
							Logger.getDefaultLogger()
									.debug(String
											.format("Connection:%s Map memory, offset:%d, size:%d",
													connectionName, bytesBegin
															+ bytesRead,
													stageToRead));
							continue;
						}

						// 写入文件
						mappedByteBuffer.put(buffer, 0, len);
						bytesRead += len;
					}
				} catch (Exception e) {
					// TODO: handle exception
					Logger.getDefaultLogger().debug(
							String.format("Connection:%s Exception caught: %s",
									connectionName, e.getMessage()));
				}

				// 释放资源
				unmapFileChannel(mappedByteBuffer);
				mappedByteBuffer = null;
				inputStream.close();
				closeConnection(context);
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				Logger.getDefaultLogger().debug(
						String.format("Connection:%s Exception caught: %s",
								connectionName, e.getMessage()));
			} catch (IOException e) {
				// TODO Auto-generated catch block
				Logger.getDefaultLogger().debug(
						String.format("Connection:%s Exception caught: %s",
								connectionName, e.getMessage()));
			} finally {

				// 关闭网络连接
				if (inputStream != null) {
					synchronized (inputStream) {
						inputStream = null;
					}
				}
				// 更新下载状态
				synchronized (XtpDownloader.this) {
					if ((bytesRead + bytesBegin) < bytesEnd) {
						// 本连接没有完成下载，重新连接
						start();
						Logger.getDefaultLogger()
								.info("Connection not disconnected, trying to reconnect.");
					} else {
						// 活跃连接数减1
						aliveConnectionCount -= 1;
						bIsRunning = false;
						if (!isDownLoading()) {

							// 所以连接已经完成
							Logger.getDefaultLogger().info(
									"All Connection's thread exited");
							bRunning = false;

							// 关闭文件
							try {
								fileChannel.close();
							} catch (IOException e) {
								// TODO Auto-generated catch block
								Logger.getDefaultLogger()
										.debug(String
												.format("Connection:%s Exception caught: %s",
														connectionName,
														e.getMessage()));
							}
							try {
								randomAccessFile.close();
								Logger.getDefaultLogger().info(
										String.format("File close: %s",
												new File(savePathString)
														.getAbsolutePath()));
							} catch (IOException e) {
								// TODO Auto-generated catch block
								Logger.getDefaultLogger()
										.debug(String
												.format("Connection:%s Exception caught: %s",
														connectionName,
														e.getMessage()));
							}
						}
					}
				}
			}
		}
	}
}
